package com.prezi.haxe.gradle;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.prezi.haxe.gradle.incubating.BinaryContainer;
import com.prezi.haxe.gradle.incubating.BinaryInternal;
import com.prezi.haxe.gradle.incubating.BinaryNamingScheme;
import com.prezi.haxe.gradle.incubating.DefaultResourceSet;
import com.prezi.haxe.gradle.incubating.FunctionalSourceSet;
import com.prezi.haxe.gradle.incubating.LanguageSourceSet;
import com.prezi.haxe.gradle.incubating.ProjectSourceSet;
import com.prezi.haxe.gradle.incubating.ResourceSet;
import com.prezi.haxe.gradle.nodetest.HaxeNodeTestCompile;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectCollection;
import org.gradle.api.DomainObjectSet;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.CopySpec;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.internal.ConventionTask;
import org.gradle.api.internal.DefaultDomainObjectSet;
import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
import org.gradle.api.internal.file.DefaultSourceDirectorySet;
import org.gradle.api.internal.file.FileResolver;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.internal.reflect.Instantiator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import org.gradle.api.internal.file.collections.DefaultDirectoryFileTreeFactory;
public class HaxeBasePlugin implements Plugin<Project> {
private static final Logger logger = LoggerFactory.getLogger(HaxeBasePlugin.class);
public static final String HAXE_SOURCE_SET_NAME = "haxe";
public static final String RESOURCE_SET_NAME = "resources";
public static final String HAXE_RESOURCE_SET_NAME = "haxeResources";
public static final String CHECK_HAXE_VERSION_TASK_NAME = "checkHaxeVersion";
public static final String COMPILE_TASK_NAME = "compile";
public static final String COMPILE_TASKS_GROUP = "compile";
public static final String CHECK_TASK_NAME = "check";
public static final String BUILD_TASK_NAME = "build";
public static final String TEST_TASK_NAME = "test";
public static final String VERIFICATION_GROUP = "verification";
private final Instantiator instantiator;
private final FileResolver fileResolver;
@Inject
public HaxeBasePlugin(Instantiator instantiator, FileResolver fileResolver) {
this.instantiator = instantiator;
this.fileResolver = fileResolver;
}
@Override
public void apply(final Project project) {
project.getPlugins().apply(BasePlugin.class);
// Add "haxe" extension
final HaxeExtension extension = project.getExtensions().create("haxe", HaxeExtension.class, project, instantiator);
final ProjectSourceSet projectSourceSet = extension.getSources();
// Add functional source sets for main code
final FunctionalSourceSet main = projectSourceSet.maybeCreate("main");
final FunctionalSourceSet test = projectSourceSet.maybeCreate("test");
logger.debug("Created {} and {} in {}", main, test, project.getPath());
final Configuration mainCompile = maybeCreateCompileConfigurationFor(project, "main");
final Configuration testCompile = maybeCreateCompileConfigurationFor(project, "test");
testCompile.extendsFrom(mainCompile);
logger.debug("Created {} and {} in {}", mainCompile, testCompile, project.getPath());
// For each source set create a configuration and language source sets
projectSourceSet.all(new Action<FunctionalSourceSet>() {
@Override
public void execute(FunctionalSourceSet functionalSourceSet) {
// Inspired by JavaBasePlugin
// Add Haxe source set for "src/<name>/haxe"
Configuration compileConfiguration = project.getConfigurations().getByName(functionalSourceSet.getName());
DefaultHaxeSourceSet haxeSourceSet = instantiator.newInstance(DefaultHaxeSourceSet.class, HAXE_SOURCE_SET_NAME, functionalSourceSet, compileConfiguration, fileResolver);
haxeSourceSet.getSource().srcDir(String.format("src/%s/haxe", functionalSourceSet.getName()));
functionalSourceSet.add(haxeSourceSet);
logger.debug("Added {} in {}", haxeSourceSet, project.getPath());
// Add resources if not exists yet
if (functionalSourceSet.findByName(RESOURCE_SET_NAME) == null) {
DefaultSourceDirectorySet resourcesDirectorySet = instantiator.newInstance(DefaultSourceDirectorySet.class, String.format("%s resources", functionalSourceSet.getName()), fileResolver, new DefaultDirectoryFileTreeFactory());
resourcesDirectorySet.srcDir(String.format("src/%s/resources", functionalSourceSet.getName()));
DefaultResourceSet resourceSet = instantiator.newInstance(DefaultResourceSet.class, RESOURCE_SET_NAME, resourcesDirectorySet, functionalSourceSet);
functionalSourceSet.add(resourceSet);
logger.debug("Added {} in {}", resourceSet, project.getPath());
}
// Add Haxe resource set to be used for embedded resources
DefaultHaxeResourceSet haxeResourceSet = instantiator.newInstance(DefaultHaxeResourceSet.class, HAXE_RESOURCE_SET_NAME, functionalSourceSet, fileResolver);
functionalSourceSet.add(haxeResourceSet);
logger.debug("Added {} in {}", haxeResourceSet, project.getPath());
}
});
NamedDomainObjectContainer<TargetPlatform> targetPlatforms = extension.getTargetPlatforms();
// For each target platform add functional source sets
targetPlatforms.all(new Action<TargetPlatform>() {
@Override
public void execute(final TargetPlatform targetPlatform) {
logger.debug("Configuring {} in {}", targetPlatform, project.getPath());
// Create platform configurations
final Configuration platformMainCompile = maybeCreateCompileConfigurationFor(project, targetPlatform.getName());
final Configuration platformTestCompile = maybeCreateCompileConfigurationFor(project, targetPlatform.getName() + "Test");
platformMainCompile.extendsFrom(mainCompile);
platformTestCompile.extendsFrom(testCompile);
platformTestCompile.extendsFrom(platformMainCompile);
logger.debug("Added {} and {} in {}", platformMainCompile, platformTestCompile, project.getPath());
final FunctionalSourceSet platformMain = projectSourceSet.maybeCreate(targetPlatform.getName());
final FunctionalSourceSet platformTest = projectSourceSet.maybeCreate(targetPlatform.getName() + "Test");
logger.debug("Added {} and {} in {}", platformMain, platformTest, project.getPath());
DomainObjectSet<LanguageSourceSet> mainLanguageSets = getLanguageSets(main, platformMain);
DomainObjectSet<LanguageSourceSet> testLanguageSets = getLanguageSets(test, platformTest);
createBinaries(project, targetPlatform.getName(), targetPlatform, null, mainLanguageSets, testLanguageSets, platformMainCompile, platformTestCompile);
// Add some flavor
targetPlatform.getFlavors().all(new Action<Flavor>() {
@Override
public void execute(Flavor flavor) {
logger.debug("Configuring {} with {} in {}", targetPlatform, flavor, project.getPath());
String flavorName = targetPlatform.getName() + Character.toUpperCase(flavor.getName().charAt(0)) + flavor.getName().substring(1);
Configuration flavorMainCompile = maybeCreateCompileConfigurationFor(project, flavorName);
Configuration flavorTestCompile = maybeCreateCompileConfigurationFor(project, flavorName + "Test");
flavorMainCompile.extendsFrom(platformMainCompile);
flavorTestCompile.extendsFrom(platformTestCompile);
flavorTestCompile.extendsFrom(flavorMainCompile);
logger.debug("Added {} and {} in {}", flavorMainCompile, flavorTestCompile, project.getPath());
FunctionalSourceSet flavorMain = projectSourceSet.maybeCreate(flavorName);
FunctionalSourceSet flavorTest = projectSourceSet.maybeCreate(flavorName + "Test");
logger.debug("Added {} and {} in {}", flavorMain, flavorTest, project.getPath());
DomainObjectSet<LanguageSourceSet> flavorMainLanguageSets = getLanguageSets(main, platformMain, flavorMain);
DomainObjectSet<LanguageSourceSet> flavorTestLanguageSets = getLanguageSets(test, platformTest, flavorTest);
createBinaries(project, flavorName, targetPlatform, flavor, flavorMainLanguageSets, flavorTestLanguageSets, flavorMainCompile, flavorTestCompile);
}
});
}
});
// Add checkHaxeVersion task
final CheckHaxeVersion checkVersionTask = project.getTasks().create(CHECK_HAXE_VERSION_TASK_NAME, CheckHaxeVersion.class);
checkVersionTask.getConventionMapping().map("compilerVersions", new Callable<Set<Object>>() {
@Override
public Set<Object> call() throws Exception {
return extension.getCompilerVersions();
}
});
checkVersionTask.setDescription("Checks if Haxe compiler is the right version.");
checkVersionTask.setGroup(VERIFICATION_GROUP);
project.getTasks().withType(AbstractHaxeCompileTask.class).all(new Action<AbstractHaxeCompileTask>() {
@Override
public void execute(AbstractHaxeCompileTask compileTask) {
compileTask.dependsOn(checkVersionTask);
}
});
// Add compile all task
Task compileTask = project.getTasks().findByName(COMPILE_TASK_NAME);
if (compileTask == null) {
compileTask = project.getTasks().create(COMPILE_TASK_NAME);
compileTask.setGroup(COMPILE_TASKS_GROUP);
compileTask.setDescription("Compile all Haxe artifacts");
}
final Task _compileTask = compileTask;
project.getTasks().withType(HaxeCompile.class).all(new Action<HaxeCompile>() {
@Override
public void execute(HaxeCompile task) {
task.setGroup(COMPILE_TASKS_GROUP);
_compileTask.dependsOn(task);
}
});
// Add test all task
Task testTask = project.getTasks().findByName(TEST_TASK_NAME);
if (testTask == null) {
testTask = project.getTasks().create(TEST_TASK_NAME);
testTask.setGroup(VERIFICATION_GROUP);
testTask.setDescription("Runs all unit tests.");
}
final Task _testTask = testTask;
Task checkTask = project.getTasks().findByName(CHECK_TASK_NAME);
if (checkTask == null) {
checkTask = project.getTasks().create(CHECK_TASK_NAME);
checkTask.setGroup(VERIFICATION_GROUP);
checkTask.setDescription("Runs all checks.");
}
checkTask.dependsOn(testTask);
project.getTasks().withType(MUnit.class).all(new Action<MUnit>() {
@Override
public void execute(MUnit task) {
task.setGroup(VERIFICATION_GROUP);
if (task.shouldRunAutomatically()) {
_testTask.dependsOn(task);
}
}
});
Task buildTask = project.getTasks().findByName(BUILD_TASK_NAME);
if (buildTask == null) {
buildTask = project.getTasks().create(BUILD_TASK_NAME);
buildTask.setDescription("Assembles and tests this project.");
buildTask.setGroup(BasePlugin.BUILD_GROUP);
}
buildTask.dependsOn(BasePlugin.ASSEMBLE_TASK_NAME);
buildTask.dependsOn(checkTask);
}
private static void createBinaries(Project project, String name, TargetPlatform targetPlatform, Flavor flavor, DomainObjectSet<LanguageSourceSet> mainLanguageSets, DomainObjectSet<LanguageSourceSet> testLanguageSets, Configuration mainConfiguration, Configuration testConfiguration) {
BinaryContainer binaryContainer = project.getExtensions().getByType(HaxeExtension.class).getBinaries();
// Add compiled binary
final HaxeBinary compileBinary = new HaxeBinary(name, mainConfiguration, targetPlatform, flavor);
final HaxeTestBinary testBinary = new HaxeTestBinary(name, testConfiguration, targetPlatform, flavor, HaxeTestCompile.class);
final HaxeTestBinary nodeTestBinary = new HaxeTestBinary("node" + name, testConfiguration, targetPlatform, flavor, HaxeNodeTestCompile.class);
mainLanguageSets.all(new Action<LanguageSourceSet>() {
@Override
public void execute(LanguageSourceSet it) {
compileBinary.getSource().add(it);
testBinary.getSource().add(it);
nodeTestBinary.getSource().add(it);
}
});
testLanguageSets.all(new Action<LanguageSourceSet>() {
@Override
public void execute(LanguageSourceSet it) {
testBinary.getSource().add(it);
nodeTestBinary.getSource().add(it);
}
});
binaryContainer.add(compileBinary);
binaryContainer.add(testBinary);
binaryContainer.add(nodeTestBinary);
logger.debug("Added binaries {} and {} in {}", compileBinary, testBinary, project.getPath());
}
private static DomainObjectSet<LanguageSourceSet> getLanguageSets(FunctionalSourceSet... functionalSourceSets) {
DomainObjectSet<LanguageSourceSet> result = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
for (FunctionalSourceSet functionalSourceSet : functionalSourceSets) {
result.add(functionalSourceSet.getByName(HAXE_SOURCE_SET_NAME));
result.add(functionalSourceSet.getByName(RESOURCE_SET_NAME));
result.add(functionalSourceSet.getByName(HAXE_RESOURCE_SET_NAME));
}
return result;
}
private static Configuration maybeCreateCompileConfigurationFor(Project project, final String name) {
Configuration config = project.getConfigurations().findByName(name);
if (config == null) {
config = project.getConfigurations().create(name);
config.setVisible(false);
config.setDescription("Compile classpath for " + name + ".");
}
return config;
}
public static <T extends HaxeTestCompile> T createTestCompileTask(final Project project, final HaxeTestBinary binary, Class<T> compileType) {
T compileTask = createCompileTask(project, binary, compileType);
compileTask.getConventionMapping().map("workingDirectory", new Callable<File>() {
@Override
public File call() throws Exception {
return project.file(project.getBuildDir() + "/haxe-test-compile/" + binary.getName());
}
});
return compileTask;
}
public static <T extends HaxeCompile> T createCompileTask(final Project project, final HaxeBinaryBase<? super T> binary, Class<T> compileType) {
BinaryNamingScheme namingScheme = ((BinaryInternal) binary).getNamingScheme();
String compileTaskName = namingScheme.getTaskName("compile");
T compileTask = createCompileTaskInternal(project, binary, compileType, compileTaskName);
project.getTasks().getByName(namingScheme.getLifecycleTaskName()).dependsOn(compileTask);
binary.setCompileTask(compileTask);
binary.builtBy(compileTask);
logger.debug("Created compile task {} for {} in {}", compileTask, binary, project.getPath());
return compileTask;
}
public static <T extends HaxeCompile> T createCompileTaskInternal(final Project project, final HaxeBinaryBase<? super T> binary, Class<T> compileType, String compileTaskName) {
final T compileTask = project.getTasks().create(compileTaskName, compileType);
compileTask.setDescription("Compiles " + binary);
compileTask.getConventionMapping().map("embeddedResources", new Callable<Map<String, File>>() {
@Override
public Map<String, File> call() throws Exception {
return gatherEmbeddedResources(binary.getSource());
}
});
compileTask.getConventionMapping().map("outputFile", new Callable<File>() {
@Override
public File call() throws Exception {
return getDefaultCompileTarget(project, binary);
}
});
compileTask.getConventionMapping().map("targetPlatform", new Callable<String>() {
@Override
public String call() throws Exception {
return binary.getTargetPlatform().getName();
}
});
compileTask.setConventionMapping(project.getExtensions().getByType(HaxeExtension.class), binary.getTargetPlatform(), binary.getFlavor());
binary.getSource().all(new Action<LanguageSourceSet>() {
@Override
public void execute(LanguageSourceSet it) {
compileTask.source(it);
}
});
compileTask.dependsOn(binary.getConfiguration());
compileTask.dependsOn(binary.getSource());
return compileTask;
}
private static File getDefaultCompileTarget(final Project project, final HaxeBinaryBase binary) {
final BinaryNamingScheme namingScheme = ((BinaryInternal) binary).getNamingScheme();
return project.file(project.getBuildDir() + "/compiled-haxe/" + namingScheme.getOutputDirectoryBase() + "/compiled." + binary.getTargetPlatform().getName());
}
public static <T extends MUnit> T createMUnitTask(final Project project, final HaxeTestBinary binary, Class<T> munitType) {
BinaryNamingScheme namingScheme = binary.getNamingScheme();
String munitTaskName = namingScheme.getTaskName("run");
T munitTask = project.getTasks().create(munitTaskName, munitType);
munitTask.setDescription("Runs MUnit on " + binary);
setMunitTaskProperties(project, binary, munitTask);
munitTask.dependsOn(binary.getCompileTask());
project.getTasks().getByName(namingScheme.getLifecycleTaskName()).dependsOn(munitTask);
logger.debug("Created munit task {} for {} in {}", munitTask, binary, project.getPath());
return munitTask;
}
private static void setMunitTaskProperties(final Project project, final HaxeTestBinary binary, ConventionTask munitTask) {
munitTask.getConventionMapping().map("workingDirectory", new Callable<File>() {
@Override
public File call() throws Exception {
return project.file(project.getBuildDir() + "/munit/" + binary.getName());
}
});
munitTask.getConventionMapping().map("targetPlatform", new Callable<String>() {
@Override
public String call() throws Exception {
return binary.getTargetPlatform().getName();
}
});
munitTask.getConventionMapping().map("inputFile", new Callable<File>() {
@Override
public File call() throws Exception {
return binary.getCompileTask().getOutputFile();
}
});
if (munitTask instanceof MUnitNode) {
munitTask.getConventionMapping().map("nodeModulesDirectory", new Callable<File>() {
@Override
public File call() throws Exception {
return project.getExtensions().getByType(HaxeExtension.class).getMunitNodeModuleInstallDir();
}
});
}
}
public static <T extends Har> T createSourceTask(final Project project, final HaxeBinaryBase<?> binary, Class<T> harType) {
final BinaryNamingScheme namingScheme = ((BinaryInternal) binary).getNamingScheme();
String sourceTaskName = namingScheme.getTaskName("bundle", "source");
T sourceTask = project.getTasks().create(sourceTaskName, harType);
sourceTask.setDescription("Bundles the sources of " + binary);
sourceTask.getConventionMapping().map("baseName", new Callable<String>() {
@Override
public String call() throws Exception {
return project.getName();
}
});
sourceTask.getConventionMapping().map("destinationDir", new Callable<File>() {
@Override
public File call() throws Exception {
return project.file(project.getBuildDir() + "/haxe-source/" + namingScheme.getOutputDirectoryBase());
}
});
sourceTask.getConventionMapping().map("embeddedResources", new Callable<Map<String, File>>() {
@Override
public Map<String, File> call() throws Exception {
return gatherEmbeddedResources(binary.getSource());
}
});
CopySpec sources = sourceTask.getRootSpec().addChild().into("sources");
sources.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
sources.from(getSources(HaxeSourceSet.class, binary));
CopySpec resources = sourceTask.getRootSpec().addChild().into(RESOURCE_SET_NAME);
resources.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
resources.from(getSources(ResourceSet.class, binary));
CopySpec embedded = sourceTask.getRootSpec().addChild().into("embedded");
embedded.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
embedded.from(Collections2.transform(binary.getSource().withType(HaxeResourceSet.class), new Function<HaxeResourceSet, Collection<File>>() {
@Override
public Collection<File> apply(HaxeResourceSet resourceSet) {
return resourceSet.getEmbeddedResources().values();
}
}));
sourceTask.dependsOn(binary.getSource());
project.getTasks().getByName(namingScheme.getLifecycleTaskName()).dependsOn(sourceTask);
binary.setSourceHarTask(sourceTask);
binary.builtBy(sourceTask);
// TODO This should state more clearly what it does
ArchivePublishArtifact artifact = (ArchivePublishArtifact) project.getArtifacts().add(binary.getConfiguration().getName(), sourceTask);
artifact.setName(project.getName() + "-" + binary.getName());
artifact.setType("har");
logger.debug("Created source source task {} for {} in {}", sourceTask, binary, project.getPath());
return sourceTask;
}
private static Collection<SourceDirectorySet> getSources(Class<? extends LanguageSourceSet> type, HaxeBinaryBase<?> binary) {
return Collections2.transform(binary.getSource().withType(type), new Function<LanguageSourceSet, SourceDirectorySet>() {
@Override
public SourceDirectorySet apply(LanguageSourceSet sourceSet) {
return sourceSet.getSource();
}
});
}
public static Map<String, File> gatherEmbeddedResources(DomainObjectCollection<LanguageSourceSet> source) {
Map<String, File> result = Maps.newLinkedHashMap();
for (HaxeResourceSet resourceSet : source.withType(HaxeResourceSet.class)) {
result.putAll(resourceSet.getEmbeddedResources());
}
return result;
}
}